/*
 * edbd_mod.c - EDB module support
 * Clunix, cezanne@clunix.com, 2005.2
 *
 * Copyright (c) 2001-2005 by Clunix Inc. All Rights Reserved.
 */

#include "config.h"

#include "edbd.h"
#include "edl.h"
#include "edbd_conn.h"
#include "reactor.h"
#include "bdexpr.h"

static ecm_mutex_t	modlock;

static ECM_INLINE void
lock_mod(void)
{
	static ECMBOOL	initialized = ECM_FALSE;
	if (!initialized) {
		modlock = ecm_mutex_create(ECM_FALSE);
		initialized = ECM_TRUE;
	}
	ecm_mutex_lock(modlock);
}

static ECM_INLINE void
unlock_mod(void)
{
	ecm_mutex_unlock(modlock);
}

static ECM_INLINE void
add_modinfo(const char *modname)
{
	sr_t	*sr_modules;

	sr_modules = sr_getc(sr_edbd_conf, "modules");
	sr_append(sr_modules, sr_create_str(ST_STR, modname));
}

sr_t *
get_modinfos(void)
{
	sr_t	*sr_modinfos;

	lock_mod();
	sr_modinfos = sr_dup(sr_getc(sr_edbd_conf, "modules"));
	unlock_mod();

	return sr_modinfos;
}

static ECMBOOL
initialize_module(edbd_conn_t edbd_conn, eds_mod_t *mod)
{
	eds_mod_t	*mod_old;
	ECMBOOL	res;

	mod_old = edbd_conn_setmodule(edbd_conn, mod);
	conn_prepare_reaction(edbd_conn);
	res = eds_exec_initializer(mod->handle);
	conn_commit_reaction(edbd_conn);
	edbd_conn_setmodule(edbd_conn, mod_old);

	return res;
}

static ECMBOOL
load_module(const char *modname)
{
	eds_mod_t	*mod;

	mod = eds_load_module(modname);
	if (mod == NULL)
		return ECM_FALSE;

#ifdef ECM_DEBUG
	{
		extern ecm_hashtable_t	mod_dbghash;
		char	*dbgset;

		dbgset = ecm_hashtable_str_get(mod_dbghash, modname);
		if (dbgset) {
			ecm_free(mod->dbgset);
			mod->dbgset = ecm_strdup(dbgset);
		}
	}
#endif
	return ECM_TRUE;
}

static void
finalize_module(edbd_conn_t edbd_conn, eds_mod_t *mod)
{
	char	*funcname;
	eds_mod_t	*mod_old;
	edb_err_t	err;

	conn_prepare_reaction(edbd_conn);

	mod_old = edbd_conn_setmodule(edbd_conn, mod);
	ecm_asprintf(&funcname, "%s_finalize", mod->modname);
	err = exec_special_edfunc(edbd_conn, funcname, NULL, NULL);
	ecm_free(funcname);

	if (err == -ERR_EDB_NOTEXIST)
		eds_exec_finalizer(mod->handle);

	edbd_conn_setmodule(edbd_conn, mod_old);
	conn_commit_reaction(edbd_conn);
}

ECMBOOL
edbd_load_module(void)
{
	sr_t	*sr_module;
	eds_mod_t		*mod;
	struct list_head	*lp;
	const char	*modname;

	/* list up module files */
	sr_module = sr_first(sr_get_child(sr_edbd_conf, "modules"));

	list_for_each(lp, &eds_modhead) {
		mod = list_entry(lp, eds_mod_t, list);
		modname = sr_get_str(sr_module);
		if (ecm_strcmp(mod->modname, modname) != 0)
			return ECM_FALSE;
		sr_module = sr_next(sr_module);
	}
	/* register modules to EDB */
	for (; sr_module; sr_module = sr_next(sr_module)) {
		modname = sr_get_str(sr_module);
		if (!load_module(modname)) {
			ECMERR("module loading error: %s", modname);
			EDBDDBG(DBG_EDBD, "error: %s", ecm_dlerror());
			return ECM_FALSE;
		}
	}
	return ECM_TRUE;
}

void
edbd_unload_module(void)
{
	eds_mod_t		*mod;
	struct list_head	*lp, *prev;

	list_for_each_tail_n(lp, &eds_modhead, prev) {
		mod = list_entry(lp, eds_mod_t, list);
		eds_unload_module(mod->modname);
	}
}

ECMBOOL
edbd_initialize_module(edbd_conn_t edbd_conn)
{
	eds_mod_t		*mod;
	struct list_head	*lp;

	list_for_each(lp, &eds_modhead) {
		mod = list_entry(lp, eds_mod_t, list);
		if (!initialize_module(edbd_conn, mod))
			return ECM_FALSE;
	}
	return ECM_TRUE;
}

void
edbd_finalize_module(edbd_conn_t edbd_conn)
{
	eds_mod_t		*mod;
	struct list_head	*lp;

	list_for_each_tail(lp, &eds_modhead) {
		mod = list_entry(lp, eds_mod_t, list);
		finalize_module(edbd_conn, mod);
	}
}

ECMBOOL
edbd_import_modules(edbd_conn_t edbd_conn, sr_t *sr_modinfos)
{
	sr_t		*sr_modinfo;
	sr_t		*sr_oct_so;
	edb_err_t	err;
	ECMBOOL		res = ECM_FALSE;

	lock_mod();

	SR_FOREACH_CHILD(sr_modinfos, NULL, sr_modinfo) {
		if (oct_to_file(edbd_modules_dir, sr_geti(sr_modinfo, 0), ECM_TRUE) < 0)
			goto out;
		sr_oct_so = sr_geti(sr_modinfo, 1);
		if (sr_get_oct_size(sr_oct_so) > 0 &&
		    ((err = oct_to_file(edbd_modules_dir, sr_oct_so, ECM_FALSE)) < 0 &&
		     err != -ERR_EDB_EXIST))
			goto out;
		add_modinfo(sr_get_name(sr_modinfo));
	}
	res = ECM_TRUE;
out:
	unlock_mod();

	return res;
}

sr_t *
edbd_export_modules(void)
{
	sr_t			*sr_modinfos, *sr_modinfo;
	sr_t			*sr_oct_so;
	eds_mod_t		*mod;
	struct list_head	*lp;

	lock_mod();

	sr_modinfos = sr_create_dir();
	list_for_each(lp, &eds_modhead) {
		mod = list_entry(lp, eds_mod_t, list);
		sr_modinfo = sr_create_dir();
		sr_set_name(sr_modinfo, mod->modname);
		sr_append(sr_modinfo, file_to_oct(mod->smod_path));
		if (mod->so_path)
			sr_oct_so = file_to_oct(mod->so_path);
		else
			sr_oct_so = sr_create_oct(ST_OCT, 0, NULL);
		sr_append(sr_modinfo, sr_oct_so);
		sr_append(sr_modinfos, sr_modinfo);
	}

	unlock_mod();

	return sr_modinfos;
}
